/* The MIT License:

Copyright (c) 2015 Ivan Gagis <igagis@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

// Home page: http://stob-cs.googlecode.com


using System;
using System.Collections.Generic;
using stob;
using System.IO;
using System.Diagnostics;
using System.Text;

namespace stob
{
	public class Node
	{
		private IList<Node> children = null;

		private void CreateChildrenCollection(){
			this.children = new List<Node> ();
		}

		public String Value;

		public Node (){}

		public Node(String value){
			this.Value = value;
		}

		public IList<Node> Children(){
			return this.children;
		}

		public void RemoveAll(){
			this.children = null;
		}

		public void Add(Node node){
			if (this.children == null) {
				this.CreateChildrenCollection ();
			}

			this.children.Add (node);
		}

		public void Remove(Node node){
			if (this.children == null) {
				return;
			}
			this.children.Remove (node);
		}

		public Node Find(String v){
			if (this.children == null || v == null) {
				return null;
			}
			foreach(Node n in this.children){
				if (v.Equals (n.Value)) {
					return n;
				}
			}
			return null;
		}

		public Node Get(String v){
			Node n = this.Find (v);
			if (n == null) {
				throw new KeyNotFoundException ();
			}
			return n;
		}

		public float AsFloat(){
			return float.Parse (this.Value);
		}

		public double AsDouble(){
			return double.Parse (this.Value);
		}

		public bool AsBool(){
			return "true".Equals (this.Value);
		}

		public int AsInt(){
			return int.Parse (this.Value);
		}

		public long AsLong(){
			return long.Parse (this.Value);
		}

		public static IList<Node> Load(StreamReader reader){
			var stack = new List<Node>();

			stack.Add (new Node ());

			Node lastParsed = null;

			Parser.Parse(
					reader,
					new Parser.Listener{
						OnChildrenParseStarted = () => {
							Debug.Assert(lastParsed != null);
							stack.Add(lastParsed);
						},
						OnChildrenParseFinished = () => {
							stack.RemoveAt(stack.Count - 1);
						},
						OnStringParsed = (str) => {
							lastParsed = new Node(str);
							stack[stack.Count - 1].Add(lastParsed);
						}
					}
				);

			Debug.Assert (stack.Count == 1);
			return stack [0].Children();
		}

		public static void Write(Stream stream, IEnumerable<Node> chain, bool formatted){
			var sw = new StreamWriter (stream);
			WriteInternal (sw, chain, formatted, 0);
			sw.Flush ();
		}


		private static void WriteInternal(StreamWriter sw, IEnumerable<Node> chain, bool formatted, int indentation){
			char quote = '"';
			char lcurly = '{';
			char rcurly = '}';
			char space = ' ';
			char tab = '\t';
			char newLine = '\n';

			//used to detect case of two adjacent unquoted strings without children, need to insert space between them
			bool prevWasUnquotedWithoutChildren = false;

			bool prevHadChildren = true;

			CanStringBeUnquotedRet csbu = new CanStringBeUnquotedRet();

			foreach(Node n in chain){
				//indent
				if(formatted){
					for(int i = 0; i != indentation; ++i){
						sw.Write(tab);
					}
				}

				//write node value

				bool unqouted = CanStringBeUnquoted(n.Value, csbu);

				if(!unqouted){
					sw.Write(quote);

					if(csbu.numEscapes == 0){
						if(n.Value != null){
							sw.Write(n.Value);
						}
					}else{
						sw.Write(MakeEscapedString(n.Value));
					}

					sw.Write(quote);
				}else{
					bool isQuotedEmptyString = false;
					if(n.Value == null || n.Value.Length == 0){//if empty string
						if((n.Children() == null || n.Children().Count == 0) || !prevHadChildren){
							isQuotedEmptyString = true;
						}
					}

					//unquoted string
					if(!formatted && prevWasUnquotedWithoutChildren && !isQuotedEmptyString){
						sw.Write(space);
					}

					if(n.Value == null || n.Value.Length == 0){//if empty string
						if(isQuotedEmptyString){
							sw.Write(quote);
							sw.Write(quote);
						}
					}else{
						Debug.Assert(csbu.numEscapes == 0);
						Debug.Assert(n.Value != null);
						sw.Write(n.Value);
					}
				}

				prevHadChildren = n.Children() != null && n.Children().Count != 0;
				if(n.Children() == null || n.Children().Count == 0){

					if(formatted){
						sw.Write(newLine);
					}
					prevWasUnquotedWithoutChildren = (unqouted && (n.Value != null && n.Value.Length != 0));
					continue;
				}else{
					prevWasUnquotedWithoutChildren = false;
				}

				if(!formatted){
					sw.Write(lcurly);

					WriteInternal(sw, n.Children(), false, 0);

					sw.Write(rcurly);
				}else{
					if(n.Children() != null && n.Children().Count == 1 && (n.Children()[0].Children() == null || n.Children()[0].Children().Count == 0)){
						//if only one child and that child has no children

						sw.Write(lcurly);
						WriteInternal(sw, n.Children(), false, 0);
						sw.Write(rcurly);
						sw.Write(newLine);
					}else{
						sw.Write(lcurly);
						sw.Write(newLine);
						WriteInternal(sw, n.Children(), true, indentation + 1);

						//indent
						for(int i = 0; i != indentation; ++i){
							sw.Write(tab);
						}
						sw.Write(rcurly);
						sw.Write(newLine);
					}
				}
			}//~for()
		}

		private class CanStringBeUnquotedRet{
			public int numEscapes;
		}

		private static bool CanStringBeUnquoted(String s, CanStringBeUnquotedRet out_ret){
			out_ret.numEscapes = 0;

			if(s == null || s.Length == 0){//empty string is can be unquoted when it has children, so return true.
				return true;
			}

			bool ret = true;
			for(int i = 0; i != s.Length; ++i /*, ++out_ret.length*/){
				char c = s [i];
				switch(c){
				default:
					break;
				case '"':
					goto case '\\';
				case '\t':
				case '\n':
				case '\r':
				case '\\':
					++out_ret.numEscapes;
				case '{':
				case '}':
				case ' ':
					ret = false;
					break;
				}
			}
			return ret;
		}

		private static String MakeEscapedString(String str){
			if(str == null){
				return "";
			}

			StringBuilder ret = new StringBuilder();

			for(int i = 0; i != str.Length; ++i){
				char c = str[i];

				switch(c){
				case '\t':
					ret.Append("\\t");
					break;
				case '\n':
					ret.Append("\\n");
					break;
				case '\r':
					ret.Append("\\r");
					break;
				case '\\':
					ret.Append("\\\\");
					break;
				case '"':
					ret.Append("\\\"");
					break;
				default:
					ret.Append(c);
					break;
				}
			}
			return ret.ToString();
		}

		public static bool CompareChains(IList<Node> a, IList<Node> b){
			if(a == null && b == null){
				return true;
			}
			if(a == null || b == null){
				return false;
			}

			if(a.Count != b.Count){
				return false;
			}

			for(int i = 0; i != a.Count; ++i){
				Node an = a[i];
				Node bn = b[i];

				if(!CompareChains(an.Children(), bn.Children())){
					return false;
				}

				if((an.Value == null || an.Value.Length == 0) && (bn.Value == null || bn.Value.Length == 0)){
					continue;
				}
				if(an.Value == null || bn.Value == null){
					return false;
				}
				if(!an.Value.Equals(bn.Value)){
					return false;
				}
			}

			return true;
		}
	}
}

